/*
   时间：2024/2/28
   程序的目的：基于管道，进行一个简单的设计：一个父进程，创建一批管道，fork创建一批子进程；父进程通过管道控制子进程
*/
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <cerrno>
#include <vector>

#include "Task.hpp"
using namespace std;
int grenum = 3; // 设计全局变量：父进程一次性创建3个子进程
task t;         // 创建全局任务

// 父进程需要对子进程进行管理：①先描述：定义类 ②再管理：使用vector进行管理
class EndPoint
{
private:
      static int number;  //对进程编号-->私有静态变量-->类外初始化
public:
    // 构建对象
    EndPoint(pid_t id, int fd): _child_id(id),_write_fd(fd)
    {
        char buffer[64];
        //进程名：process-0[pid,fd]
        snprintf(buffer,sizeof(buffer),"process-%d[%d,%d]",number++,_child_id,_write_fd);
        processname = buffer;
    }
    //设置方法使类外也可以获取进程名
    string name()  const
    {
        return processname;
    }
    ~EndPoint()
    {
    }
public:
    // 定义变量
    pid_t _child_id; // 子进程的id
    int _write_fd;   // 管道的写入端的标识符
    string processname; //给子进程的设置特定的进程名
};
int EndPoint::number = 0; //初始化静态成员变量
void Waitcommand()
{ // 子进程开始等待获取命令
    while (true)  //不断读取
    {
        int command = 0;
        int n = read(0, &command, sizeof(int)); //read是字节流读取
        // 根据读取的命令，进行什么样的操作：TODO--写任务
        if(n == 4)   //读取成功
        {
          t.Excute(command);
        }else if(n == 0)
        {
            cout<<"父进程：write quit,me too!"<<endl;
            break;
        }
        else  //读取失败
        {
            break;
        }
    }
}

void ctrprocess(vector<EndPoint> *endpoints)
{
    vector<int> fd;  //将父进程创建管道的写入端的记录保存到fd
    // 1.先构建控制结构，父进程写入，子进程读取
    for (int i = 0; i < grenum; i++)
    {
        // 1.1创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);
        assert(n != -1); // 如果n==-1,管道创建失败，assert被触发
        (void)n;        //防止release版本不通过

        // 1.2创建子进程
        pid_t id = fork();
        if(id == -1) 
        {
            cerr<<"fork error"<<endl;
            return;
        }
        if (id == 0) // 子进程
        {

            if(!fd.empty())
             {
              for(auto e : fd)  close(e);  //子进程主动关闭继承父进程的写入端，取消指向
            }

            // cout<<"子进程pid："<<getpid()<<"关闭继承父进程的写入端：";
            // if(!fd.empty())
            // {
            //   for(auto e : fd)  //子进程主动关闭继承父进程的写入端，取消指向
            //   {
            //      close(e); 
            //      cout<<e<<" ";
            //   }
            // }
            // cout<<endl;

            // 1.3父子进程关闭不需要的fd
            close(pipefd[1]); // 子进程关闭写入端
            // 我们期望，所有子进程读取“指令”的时候，都从标准输入读取
            // 1.3.1输入重定向
            dup2(pipefd[0], 0);
            //cout<<"子进程pid:："<<getpid()<<"：pipefd[0]->"<<pipefd[0]<<endl;  //查子进程读取端的标识符
            // 1.3.2子进程开始等待获取父进程指令
            Waitcommand();

            close(pipefd[0]); // 读取完之后将读取端关闭
            exit(0);
        }
        // 父进程
        // 1.3父子进程关闭不需要的fd
        close(pipefd[0]);                              // 父进程关闭读取端
        endpoints->push_back(EndPoint(id, pipefd[1])); // 将创建的进程id和管道写入端标识符放到vector进行管理

        fd.push_back(pipefd[1]);  //将父进程创建管道的写入端的记录保存到fd
    }
}

//指令的控制面板
int  ShowBoard()
{
    cout<<"###################################"<<endl;
    cout<<"#0-执行日志任务     1-执行数据库任务#"<<endl;
    cout<<"#2-执行请求任务     3-退出         #"<<endl;
    cout<<"###################################"<<endl;
    int command = 0;
    cin>>command;
    return command;
}
//2.对子进程进行命令控制
void ctrProcess(const vector<EndPoint>& endpoints)
{
      int num = 0;
      int cnt = 0;  //按顺序选择进程
    while(true)  //持续写入
    {
        //2.1选择任务  --> 交互式的形式
        int command =  ShowBoard();
         if(command == 3) break;
         if(command < 0 || command > 2) continue;
        //2.2选择进程
        //int i = rand()%endpoints.size();
        int index = cnt++;
        cout<<"index:"<<index<<endl;
        cnt %= endpoints.size();
        //2.3下发任务
        cout<<"选择了进程："<<endpoints[index].name()<<" | 处理任务："<<command<<endl;
        ssize_t bytes_written = write(endpoints[index]._write_fd,&command,sizeof(command));
        if(bytes_written == -1)
        {
            cerr<<"write error"<<endl;
            return;
        }
        //if(num++ > 10) break;   //测试：当父进程写入端退出时，子进程是什么情况？
        sleep(1);
    }
}

//3.处理所有退出问题
void Waitprocess(vector<EndPoint>& endpoints)
{
     //解法一：倒着关闭
    //  for(int end = endpoints.size()-1;end >= 0;end--)
    //  {
    //      cout<<"关闭进程pid:"<<getpid()<<"的写入端fd："<<endpoints[end]._write_fd<<endl;
    //      close(endpoints[end]._write_fd);
    //      waitpid(endpoints[end]._child_id,nullptr,0);
    //      cout<<"已经回收了一个子进程"<<endl;
    //  }

    //解法二：修改ctrprocess中的代码--》一般采用这个：子进程主动关闭父进程历史的写入端
    for(auto& e : endpoints)
    {
        cout<<"关闭父进程进程pid:"<<getpid()<<"的写入端fd："<<e._write_fd<<endl;
        close(e._write_fd);
        waitpid(e._child_id,nullptr,0);
        cout<<"已经回收了一个子进程"<<endl;
    }
     

    //如果不修改ctrprocess，这样关闭，存在问题--因为子进程会继承父进程的进程内容，由于管道原理，新创建子进程会指向父进程历史指向的写入端fd
    // for(auto& e : endpoints)
    // {
    //     cout<<"关闭进程pid:"<<getpid()<<"的写入端fd："<<e._write_fd<<endl;
    //     close(e._write_fd);
    //     waitpid(e._child_id,nullptr,0);
    //     cout<<"已经回收了一个子进程"<<endl;
    // }

    // cout<<"父进程关闭所有的写入端"<<endl;
    //  //3.1我们需要所有的子进程退出--只需要让父进程关闭所有的write fd就行
    //  for(auto& e : endpoints) close(e._write_fd);
    //  sleep(10);  //延时便于观察
    //  //3.2父进程回收所有子进程的僵尸状态
    //  for(auto& e : endpoints) waitpid(e._child_id,nullptr,0);
    //  cout<<"父进程回收所有的子进程"<<endl;
    //  sleep(5);   //延时便于观察

}

int main()
{
    // 使用vector对子进程进行管理
    vector<EndPoint> endpoints;
    // 1.先构建控制结构，父进程写入，子进程读取
    ctrprocess(&endpoints);
    // sleep(10);  // 测试查看子进程是否创建成功
    //sleep(1);
    //2.我们得到了子进程的id和管道的写入端标识符，对子进程进行命令控制
    ctrProcess(endpoints);

    //3.处理所有的退出问题
    Waitprocess(endpoints);
    return 0;
}